﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Forums
{
    using System;
    using System.Collections.Generic;
    using System.Data.Linq;
    using System.Data.Linq.Mapping;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;

    /// <summary>See <see cref="IGroup"/>.</summary>
    internal partial class Group : IGroup
    {
        private string dataDirectory;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="IGroup.InboundArticleFilter"/>.</summary>
        public InboundArticleFilter InboundArticleFilter
        {
            get
            {
                return new InboundArticleFilter(
                    this.IsOriginalInboundArticleBeingWatchedFilter,
                    this.HasOriginalInboundArticleBeenReadFilter,
                    this.IsFollowupInboundArticleBeingWatchedFilter,
                    this.HasFollowupInboundArticleBeenReadFilter);
            }

            set
            {
                this.AssertValid();
                this.IsOriginalInboundArticleBeingWatchedFilter = value.IsOriginalBeingWatched;
                this.HasOriginalInboundArticleBeenReadFilter = value.HasOriginalBeenRead;
                this.IsFollowupInboundArticleBeingWatchedFilter = value.IsFollowupBeingWatched;
                this.HasFollowupInboundArticleBeenReadFilter = value.HasFollowupBeenRead;
            }
        }

        /// <summary>See <see cref="IGroup.GetOriginalInboundArticles(InboundArticleFilter)"/>.</summary>
        public IEnumerable<IInboundArticle> GetOriginalInboundArticles(InboundArticleFilter filter)
        {
            return this.Root.GetOriginalInboundArticles(this, filter);
        }

        /// <summary>See <see cref="IGroup.GetOriginalInboundArticleCount(InboundArticleFilter)"/>.</summary>
        public int GetOriginalInboundArticleCount(InboundArticleFilter filter)
        {
            return this.Root.GetOriginalInboundArticleCount(this, filter);
        }

        /// <summary>See <see cref="IGroup.MarkAllInboundArticlesAsRead"/>.</summary>
        public void MarkAllInboundArticlesAsRead()
        {
            var root = this.Root;
            root.Attach(
                new InboundArticleMarkAllAsRead() { GroupId = this.Id, HasBeenRead = true },
                new InboundArticleMarkAllAsRead() { GroupId = this.Id, HasBeenRead = false });
            root.ReopenDatabaseAfterCommit();
        }

        /// <summary>See <see cref="IDeletablePersistentObject.Delete"/>.</summary>
        public void Delete()
        {
            var root = this.Root;

            // We must ensure that there's at least one row, otherwise the update of ReferencedId will fail with
            // a ChangeConflictException
            if (root.GetOriginalInboundArticleCount(this, InboundArticleFilter.NullFilter) > 0)
            {
                // Different entity classes are necessary for the update and the deletion, otherwise the update is
                // ignored and only the deletion is executed (which will typically fail to execute because there's
                // almost always at least one entity where ReferencedId != null).
                root.Attach(
                    new InboundArticleSetReferencedId() { GroupId = this.Id, ReferencedId = null },
                    new InboundArticleSetReferencedId() { GroupId = this.Id, ReferencedId = 1 });
                var entity = new InboundArticleMarkAllAsRead() { GroupId = this.Id };
                root.Attach(entity);
                root.DeleteOnSubmit(entity);
                root.InsertOnSubmit(new DeleteFile() { Path = this.DataDirectory });
            }

            root.DeleteOnSubmit(this);
            root.ReopenDatabaseAfterCommit();
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="IGroup.Host"/>.</summary>
        IHost IGroup.Host
        {
            get { return EntityHelper.AssertNotDeleted(this.Host); }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal Root Root
        {
            get { return EntityHelper.AssertNotDeleted(this.Host).Root; }
        }

        internal string DataDirectory
        {
            get
            {
                if (this.dataDirectory == null)
                {
                    this.dataDirectory = Path.Combine("In", this.Id.ToString(CultureInfo.InvariantCulture));
                }

                return this.dataDirectory;
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void AssertValid()
        {
            this.Root.AssertValid();
        }

        /// <summary>This fake entity is used to set the ReferenceId column of all articles associated with a particular group.</summary>
        [Table(Name = "InboundArticle")]
        private sealed class InboundArticleSetReferencedId
        {
            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called through reflection.")]
            [Column(IsPrimaryKey = true)]
            internal int GroupId { get; set; }

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called through reflection.")]
            [Column(UpdateCheck = UpdateCheck.Never)]
            internal int? ReferencedId { get; set; }
        }

        /// <summary>This fake entity is used to set the HasBeenRead column of all articles associated with a particular
        /// group and also delete all articles of the group.</summary>
        [Table(Name = "InboundArticle")]
        private sealed class InboundArticleMarkAllAsRead
        {
            private EntityRef<Group> group;

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called through reflection.")]
            [Column(IsPrimaryKey = true)]
            internal int GroupId { get; set; }

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called through reflection.")]
            [Column(UpdateCheck = UpdateCheck.Never)]
            internal bool HasBeenRead { get; set; }

            // This association is necessary to ensure that InboundArticle rows are deleted *before* their referenced Group row
            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called through reflection.")]
            [Association(Name = "Group_InboundArticle2", Storage = "group", ThisKey = "GroupId", IsForeignKey = true)]
            internal Group Group
            {
                get { return this.group.Entity; }
                set { this.group.Entity = value; }
            }
        }
    }
}
